﻿/*
 * @author: wizardc
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Dou.Operate;
using Dou.Utils;
using UnityEngine;

namespace Dou.Resource
{
    /// <summary>
    /// 加载 AB 包
    /// </summary>
    internal class AssetBundleLoadOperate
    {
        private static readonly ObjectPool _callbackPool = new ObjectPool(() => new List<Action<AssetBundle>>());
        
        private ObjectPool _pool = new ObjectPool(() => new AssetBundleLoadData());
        private OperateQueue _queue = new OperateQueue();
        
        private Dictionary<string, List<Action<AssetBundle>>> _loadingMap = new Dictionary<string, List<Action<AssetBundle>>>();
        
        public AssetBundleLoadOperate()
        {
            _queue.RegisterExecutor(typeof(AssetBundleLoadData), typeof(AssetBundleLoadExecutor));
        }
        
        public int maxLoadThread
        {
            set => _queue.parallelMaxCount = Mathf.Clamp(value, 1, 100);
            get => _queue.parallelMaxCount;
        }
        
        public void Load(string name, Action<AssetBundle> onComplete, int priority, bool isSync, bool isEncrypt)
        {
            if (_loadingMap.ContainsKey(name))
            {
                _loadingMap[name].Add(onComplete);
                return;
            }
            
            _loadingMap.Add(name, (List<Action<AssetBundle>>) _callbackPool.Take());
            _loadingMap[name].Add(onComplete);
            
            var data = (AssetBundleLoadData) _pool.Take();
            data.priority = priority;
            data.key = name;
            data.isSync = isSync;
            if (isEncrypt)
            {
                var fileName = Path.GetFileName(name);
                data.byteOffset = (uint) fileName.Length;
            }
            else
            {
                data.byteOffset = 0;
            }
            data.onComplete = OnLoadComplete;
            
            _queue.AddOperate(data);
        }

        private void OnLoadComplete(OperateData operateData, bool success, string error)
        {
            var data = (AssetBundleLoadData) operateData;
            var name = data.key;
            var list = _loadingMap[name];
            if (success)
            {
                foreach (var onComplete in list)
                {
                    onComplete(data.assetBundle);
                }
            }
            else
            {
                Log.Error(error);
            }
            list.Clear();
            _loadingMap.Remove(name);
            _callbackPool.Join(list);
        }
    }

    internal class AssetBundleLoadData : OperateData
    {
        /// <summary>
        /// 是否是同步加载
        /// </summary>
        public bool isSync;
        
        /// <summary>
        /// 加密后需要跳过的字节数
        /// </summary>
        public uint byteOffset = 0;

        /// <summary>
        /// 加载后的 AB 对象
        /// </summary>
        public AssetBundle assetBundle;

        public override void OnRecycle()
        {
            base.OnRecycle();
            
            isSync = false;
            byteOffset = 0;
            assetBundle = null;
        }
    }

    internal class AssetBundleLoadExecutor : IOperateExecutor
    {
        public void Execute(OperateData operateData, Action<IOperateExecutor, OperateData, bool, string> onComplete)
        {
            var data = (AssetBundleLoadData) operateData;
            var filePath = data.key;
            
            if (data.isSync)
            {
                try
                {
                    data.assetBundle = AssetBundle.LoadFromFile(filePath, 0, data.byteOffset);
                    onComplete(this, data, true, String.Empty);
                }
                catch (Exception e)
                {
                    onComplete(this, data, false, $"加载的AB文件\"{filePath}\"报错：{e.Message}\r{e.StackTrace}");
                }
            }
            else
            {
                CoroutineUtil.StartCoroutine(LoadAssetBundle(data, onComplete));
            }
        }

        private IEnumerator LoadAssetBundle(AssetBundleLoadData data, Action<IOperateExecutor, OperateData, bool, string> onComplete)
        {
            var request = AssetBundle.LoadFromFileAsync(data.key, 0, data.byteOffset);
            if (request == null)
            {
                onComplete(this, data, false, $"加载的AB文件\"{data.key}\"异常！");
                yield break;
            }
            yield return request;
            if (!request.isDone)
            {
                onComplete(this, data, false, $"加载的AB文件\"{data.key}\"异常！");
                yield break;
            }
            data.assetBundle = request.assetBundle;
            onComplete(this, data, true, String.Empty);
        }

        public void OnRecycle()
        {
        }

        public void OnReuse()
        {
        }
    }
}
